package model;
import java.io.*;
import java.util.List;
import java.util.ArrayList;

/**
 * GameLevel contains all the information needed
 * for a complete level for turn based pacman. This includes
 * the game maze itself, as well as the starting tiles for
 * the player and the ghosts
 * 
 * @author Alexander Craig
 */
public class GameLevel {
	
	/** XML Tags. */
	private static final String TILE_XML_TAG = "tile";
	private static final String LEVEL_XML_TAG = "level";
	
	/** Various error messages. */
	private static final String ERROR_CLOSING_INPUT_MSG = "Error closing input stream.";
	private static final String COULD_NOT_READ_ERROR = "Could not read from file.";
	private static final String ERROR_CLOSING_OUTPUT_MSG = "Error closing output streams.";
	private static final String ERROR_WRITING_MSG = "Error writing to file.";
	private static final String ERROR_GENERATING_MSG = "Error generating level file.";
	private static final String XML_HEADER = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";

	/** stores the game grid */
	private Maze maze;
	
	/** the name of the level */
	private String title;
	
	/** 
	 * Stores the starting location of the player. Note that this is
	 * stored separately from other spawn points, as only one can ever exist
	 * in a level.
	 */
	private Tile playerStart;
	
	/** stores the spawn points of all game entities */
	private List<SpawnPoint> spawnPoints;
	
	/**
	 * Generates an empty game level
	 */
	public GameLevel() {
		this.maze = null;
		this.title = null;
		playerStart = null;
		spawnPoints = new ArrayList<SpawnPoint>();
	}
	
	/**
	 * Generates a new level with the given maze.
	 * This maze should not be replaced once the level object has
	 * been created.
	 * @param maze	the game maze to use for this level
	 */
	public GameLevel(Maze maze, String title) {
		this.maze = maze;
		this.title = title;
		playerStart = null;
		spawnPoints = new ArrayList<SpawnPoint>();
	}
	
	/**
	 * @return	the title of the level
	 */
	public String getTitle() {
		return title;
	}
	
	/**
	 * @return true if every tile on the maze has been visited, false if not
	 */
	public boolean isComplete() {
		return (maze.dotsRemaining() == 0);
	}
	
	/**
	 * Returns the underlying maze structure. This will most likely be
	 * required by the view to draw the game area.
	 * @return the underlying maze structure
	 */
	public Maze getMaze() {
		return maze;
	}
	
	/**
	 * Checks if a tile is already being used as a spawn tile.
	 * @param t	the tile to check
	 * @return	true if the tile has already been used as a spawn tile
	 */
	public boolean isSpawnTile(Tile t) {
		if (t == playerStart) {
			return true;
		}
		
		for (SpawnPoint s : spawnPoints) {
			if (s.getSpawnTile() == t) {
				return true;
			}
		}
		
		return false;
	}
	
	/**
	 * Removes any spawn points that references the passed tile.
	 * @param t	the tile to remove spawn points for.
	 */
	public void removeSpawnTile(Tile t) {
		if (t == playerStart) {
			playerStart = null;
		}
		
		SpawnPoint s;
		for(int i = 0; i < spawnPoints.size(); i++) {
			s = spawnPoints.get(i);
			if (s.getSpawnTile() == t) {
				spawnPoints.remove(i);
				i--;
			}
		}
	}
	
	/**
	 * @return	true if the level is a valid, playable level, false if not
	 */
	public boolean isValid() {
		if(playerStart != null && maze != null) {
			return true;
		}
		return false;
	}
	
	/**
	 * Sets the player start to the passed tile. No function if null.
	 * @param start	the tile for the player to spawn at
	 * @return true if the set succeeded, false if not (overlaps another spawn)
	 */
	public boolean setPlayerStart(Tile start) {
		if (start != null) {
			for(SpawnPoint s : spawnPoints) {
				if(s.getSpawnTile() == start) {
					return false;
				}
			}
			playerStart = start;
			return true;
		}
		return false;
	}
	
	/**
	 * Clears the player starting point.
	 */
	public void clearPlayerStart() {
		playerStart = null;
	}
	
	/**
	 * Adds a new spawn point to the level.
	 * @param tile	the tile at which to add a spawn point
	 * @param spawnType	the type of game entity to spawn at this tile
	 * @return true if the add succeeded, false if not (duplicate tile)
	 */
	public boolean addSpawnPoint(Tile tile, GameEntity spawnType) {
		if (tile != null && spawnType != null && tile != playerStart) {
			for(SpawnPoint s : spawnPoints) {
				if(s.getSpawnTile() == tile) {
					return false;
				}
			}
			spawnPoints.add(new SpawnPoint(tile, spawnType));
			return true;
		}
		return false;
	}
	
	/**
	 * Removes the specified spawn point from the level.
	 * @param index	the index of the spawn point to remove
	 * @return	false if an invalid index was passed, true
	 * 			if the remove was successful
	 */
	public boolean clearSpawnPoint(int index) {
		if (index >= 0 && index < spawnPoints.size()) {
			spawnPoints.remove(index);
			return true;
		}
		return false;
	}
	
	/**
	 * @return the number of spawn points present in the level.
	 */
	public int getNumSpawnPoints() {
		return spawnPoints.size();
	}
	
	/**
	 * @return	the starting tile of the player, or null if none has
	 * 			been defined
	 */
	public Tile getPlayerStart() {
		return playerStart;
	}
	
	/**
	 * Returns the specified ghost starting point.
	 * @param index	the index of the ghost start to return
	 * @return	null if invalid index is passed, or a ghost
	 * 			starting tile otherwise
	 */
	public SpawnPoint getSpawnPoint(int index) {
		if (index >= 0 && index < spawnPoints.size()) {
			return spawnPoints.get(index);
		}
		return null;
	}
}
